package simulation;

import streaming.Host;
import streaming.Overlay;
import streaming.StreamingConfig;

public class Message implements EventHandler {

	Host source;
	Host dest;
	String type;
	String data;
	int seqNo;
	int[] listSeqNo;
	boolean messageFromCM;
	
	/**
	 * Created to send messages informing a single seqNo
	 * @param command Command to be executed
	 * @param newSource Source of the message
	 * @param newDest Destination of the message
	 * @param newSeqNo Sequence number of the chunk to be sent
	 */
	public Message(String command, Host newSource, Host newDest, int newSeqNo, boolean messageFromCM){
		this.setSource(newSource);
		this.setDest(newDest);
		this.setMessageFromCM(messageFromCM);
		
		// If the source doesn't have the destination as a neighbor anymore (due to node removal, for example), ignore this message
		if (this.getSource().getNeighborsMap().containsKey(this.getDest().getId())){
			double latency = this.getSource().getNeighborLatency(this.getDest().getId());
			if (latency == -1) {
				latency = Overlay.getInstance().getLatency(this.getSource(), this.getDest());
				this.getSource().setNeighborLatency(this.getDest().getId(), latency);
				
				// Source's neighbors are not reciprocal
				if (this.getSource().getId() != 0) {
					this.getDest().setNeighborLatency(this.getSource().getId(), latency);
				}
			}
			// Put receipt of packet in the queue
			Simulator sim = Simulator.getInstance();
			sim.addEvent(sim.getTime() + latency, this) ;
			seqNo= newSeqNo;
			type = command;
		}
		else {
			System.out.println("Host " + this.getDest().getId() + " couldn't be found, isn't anymore a neighbor of host "+ this.getSource().getId());
		}
	}
	
	/**
	 * Created to send messages informing a list of seqNos
	 * @param command Command to be executed
	 * @param newSource Source of the message
	 * @param newDest Destionation of the message
	 * @param newSeqNo Sequence number of the chunk to be sent
	 */
	public Message(String command, Host newSource, Host newDest, int[] newSeqNo){
		this.setSource(newSource);
		this.setDest(newDest);
		
		// If the source doesn't have the destination as a neighbor anymore (due to node removal, for example), ignore this message
		if (this.getSource().getNeighborsMap().containsKey(this.getDest().getId())){
			double latency = this.getSource().getNeighborLatency(this.getDest().getId());
			if (latency == -1) {
				/* TODO Verificar, esse método getLatency está sempre retornando 0.5 0.o */
				latency = Overlay.getInstance().getLatency(this.getSource(), this.getDest());
				this.getSource().setNeighborLatency(this.getDest().getId(), latency);
				
				// Source's neighbors are not reciprocal
				if (this.getSource().getId() != 0) {
					this.getDest().setNeighborLatency(this.getSource().getId(), latency);
				}
			}
			// Put receipt of packet in the queue
			Simulator sim = Simulator.getInstance();
			sim.addEvent(sim.getTime() + latency, this) ;
			listSeqNo= newSeqNo;
			type = command;
		}
		else {
			System.out.println("Host " + this.getDest().getId() + " couldn't be found, isn't anymore a neighbor of host "+ this.getSource().getId());
		}
	}
	
	/**
	 * Created to send messages with the data inside the chunk
	 * @param command Command to be executed
	 * @param newSource Source of the message
	 * @param newDest Destionation of the message
	 * @param newSeqNo Sequence number of the chunk to be sent
	 * @param data Data of the chunk
	 */
	public Message(String command, Host newSource, Host newDest, int newSeqNo, String data, boolean messageFromCM){
		this.setSource(newSource);
		this.setDest(newDest);
		this.setMessageFromCM(messageFromCM);
		
		// If the source doesn't have the destination as a negihbor anymore (due to node removal, for example), ignore this message
		if (this.getSource().getNeighborsMap().containsKey(this.getDest().getId())){
			double latency = this.getSource().getNeighborLatency(this.getDest().getId());
			if (latency == -1) {
				latency = Overlay.getInstance().getLatency(this.getSource(), this.getDest());
				this.getSource().setNeighborLatency(this.getDest().getId(), latency);
				
				// Source's neighbors are not reciprocal
				if (this.getSource().getId() != 0) {
					this.getDest().setNeighborLatency(this.getSource().getId(), latency);
				}
			}
			this.seqNo= newSeqNo;
			this.type = command;
			this.data = data;
			
			// Put receipt of packet in the queue
			Simulator sim = Simulator.getInstance();
			sim.addEvent(sim.getTime() + latency, this) ;
		}
		else {
			System.out.println("Host " + this.getDest().getId() + " couldn't be found, isn't anymore a neighbor of host "+ this.getSource().getId());
		}
	}
	
	// Receipt of packet
	public void run(double time){
		if (this.getDest().getId() == -1)
			return;
		if (this.getType().equals("notify")){
			this.getDest().handleNotify(this.getListSeqNo(), this.getSource());
		}
		else if (this.getType().equals("request")) {
			if (this.isMessageFromCM()){
				Simulator.getInstance().addIntDiagnosticRequestMessages();
			}
			else {
				Simulator.getInstance().addIntMessagesSimplesSimulation();
			}
			
			this.getDest().handleRequest(this.getSeqNo(), this.getSource(), this.isMessageFromCM());
		}
		else if (this.getType().equals("packet")) {
			if (this.isMessageFromCM()){
				Simulator.getInstance().addIntDiagnosticResponseMessages();
				
				// Check whether the time is not greater than the array size to avoid execution problems (Array out of bounds)
				if (Simulator.getInstance().getTimeTrunc() < (Config.TIME + StreamingConfig.MCASTWINDOWSIZE + 1)){
					// For simulation statistics, add the second when the chunk was sent by the Comparator Module
					Simulator.getInstance().chunksSentByCMPerSecond[Simulator.getInstance().getTimeTrunc()]++;
				}
			}
			else {
				Simulator.getInstance().addIntMessagesSimplesSimulation();
				Simulator.getInstance().addIntTotalChunksSent();
				
				
				// Check whether the time is not greater than the array size to avoid execution problems (Array out of bounds)
				if (Simulator.getInstance().getTimeTrunc() < (Config.TIME + StreamingConfig.MCASTWINDOWSIZE + 1)){
					// For simulation statistics, add the second when the chunk was sent by the system (excluding the ones sent by Comparator Module)
					Simulator.getInstance().chunksSentWithoutCMPerSecond[Simulator.getInstance().getTimeTrunc()]++;
				}
			}
			
			this.getDest().handlePacket(this.getSeqNo(), this.getSource(), this.data, this.isMessageFromCM());
		}
	}

	/* Getters and Setters */
	
	public Host getSource() {
		return source;
	}

	public void setSource(Host source) {
		this.source = source;
	}

	public Host getDest() {
		return dest;
	}

	public void setDest(Host dest) {
		this.dest = dest;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public int getSeqNo() {
		return seqNo;
	}

	public void setSeqNo(int seqNo) {
		this.seqNo = seqNo;
	}

	public int[] getListSeqNo() {
		return listSeqNo;
	}

	public void setListSeqNo(int[] listSeqNo) {
		this.listSeqNo = listSeqNo;
	}

	public boolean isMessageFromCM() {
		return messageFromCM;
	}

	public void setMessageFromCM(boolean messageFromCM) {
		this.messageFromCM = messageFromCM;
	}
}
